using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

using SlimDX;
using SlimDX.Direct3D9;
using SlimDX.Windows;

using miciv;
using miciv.Geometry;
using miciv.Rendering;

namespace micivRenderers.Renderers
{
    public class TextureDiffuseRenderer : RendererBase, IManagedResource
    {
        public struct Vertex
        {
            public Vector3 Position;
            public Color4 Color;
            public Vector2 UV;
        }

        protected class VertexFeeder : VertexFeederBase<Vertex, int>
        {
            protected Vertex[] m_vertices;
            protected int[] m_indices;

            public VertexFeeder(ManagedVertexBuffer<Vertex> _vertexBuffer, ManagedIndexBuffer<int> _indexBuffer)
                : base(_vertexBuffer, _indexBuffer)
            {
                this.m_vertices = new Vertex[this.VertexCount];
                this.m_vertexBuffer.Vertices = this.m_vertices;
                if (null != this.IndexBuffer)
                {
                    this.m_indices = new int[this.IndexCount];
                    this.IndexBuffer.Indices = this.m_indices;
                }
            }

            override public void SetVertex(uint _uIndex, System.Collections.Hashtable _htblData)
            {
                if (this.VertexCount > _uIndex)
                {
                    this.m_vertexBuffer.Vertices[_uIndex].Position = (Vector3)_htblData["POSITION"];
                    this.m_vertexBuffer.Vertices[_uIndex].Color = (Color4)_htblData["COLOR"];
                    this.m_vertexBuffer.Vertices[_uIndex].UV = (Vector2)_htblData["UV"];
                }
            }
        }

        protected CoreContext m_coreContext;
        protected Device m_d3dDevice;
        protected EffectHandle m_hWorldViewProj;
        protected EffectHandle m_hAlbedoTexture;
        protected EffectHandle m_hDiffuseColor;
        protected VertexBufferManager<Vertex> m_vbManager;
        protected IndexBufferManager<int> m_ibManager;

        public TextureDiffuseRenderer()
        {
        }

        public bool Initialize(CoreContext _coreContext, int _sMaxVertexCount, int _sMaxIndexCount)
        {
            this.m_coreContext = _coreContext;
            this.m_coreContext.GetData<ManagedResourceManager>("ManagedResourceManager").Add(this);
            this.m_d3dDevice = this.m_coreContext.GetData<Device>("D3DDevice");
            this.m_vbManager = new VertexBufferManager<Vertex>();
            this.m_vbManager.Initialize(this.m_coreContext, _sMaxVertexCount);
            this.m_ibManager = new IndexBufferManager<int>();
            this.m_ibManager.Initialize(this.m_coreContext, _sMaxIndexCount);
            this.DeviceReset();
            return true;
        }

        #region IRenderer Membres

        override public bool CanSupportVertexDefinition(VertexDefinition _vertexFormat)
        {
            if ((3 != _vertexFormat.ElementCount) || (2 != _vertexFormat.InterfaceCount))
            {
                return false;
            }

            if ((false == _vertexFormat.HasElement("POSITION", typeof(Vector3)))
                || (false == _vertexFormat.HasElement("COLOR", typeof(Color4)))
                || (false == _vertexFormat.HasElement("UV", typeof(Vector2))))
            {
                return false;
            }

            if ((false == _vertexFormat.HasInterface("IAlbedoTexture"))
                || (false == _vertexFormat.HasInterface("IDiffuse")))
            {
                return false;
            }

            return true;
        }

        override public bool CanSupportRenderable(IRenderable _renderable)
        {
            return ((null != (_renderable as IAlbedoTexture))
                && (null != (_renderable as IDiffuse)));
        }

        override public bool CanSupportRenderableType(Type _type)
        {
            return ((null != _type.GetInterface("IAlbedoTexture"))
                && (null != _type.GetInterface("IDiffuse")));
        }

        override public void BeginScene()
        {
            base.BeginScene();
            this.m_vbManager.Update();
            this.m_ibManager.Update();
        }

        override public void BeginRender()
        {
            base.BeginRender();
            Result result = this.m_d3dDevice.SetStreamSource(0, this.m_vbManager.VertexBuffer, 0, Marshal.SizeOf(typeof(Vertex)));
            if (false != result.IsFailure)
            {
                System.Diagnostics.Debug.WriteLine(string.Format("{0} : {1}", result.Name, result.Description));
            }
            this.m_d3dDevice.Indices = this.m_ibManager.IndexBuffer;
        }

        override public void RenderAllOpaques()
        {
            Matrix matWorld;
            for (int i = 0; this.m_sPasses > i; ++i)
            {
                this.m_effect.BeginPass(i);
                foreach (IRenderable renderable in this.m_lstRenderables.Reverse)
                {
                    // check and skip any renderable outside current renderer context scop
                    if ((null != renderable.RestrictedRendererContexts) && (false == renderable.RestrictedRendererContexts.Contains(this.RendererContext)))
                    {
                        continue;
                    }
                    // check and skip any renderable with transparency activated
                    IDiffuse diffuse = renderable as IDiffuse;
                    if ((null != diffuse) /*&& (0.0 != diffuse.Diffuse.Alpha)*/ && (1.0 != diffuse.Diffuse.Alpha))
                    {
                        continue;
                    }
                    PrimitiveType primType;
                    int sCount;
                    matWorld = renderable.World;
                    this.m_effect.SetValue<Matrix>(this.m_hWorldViewProj, this.RendererContext.Camera.ComputeWorldViewProj(ref matWorld, true));
                    this.m_effect.SetTexture(this.m_hAlbedoTexture, (renderable as IAlbedoTexture).Albedo.Texture);
                    this.m_effect.SetValue<SlimDX.Vector4>(this.m_hDiffuseColor, (renderable as IDiffuse).Diffuse.ToVector4());
                    this.m_effect.CommitChanges();
                    if ((null != renderable.VertexFeeders) && (0 < renderable.VertexFeeders.Count))
                    {
                        foreach (VertexFeeder vertexFeeder in renderable.VertexFeeders)
                        {
                            ManagedVertexBuffer<Vertex> mVB = vertexFeeder.VertexBuffer;
                            if (0 != vertexFeeder.IndexCount)
                            {
                                ManagedIndexBuffer<int> mIB = vertexFeeder.IndexBuffer;
                                this.m_d3dDevice.DrawIndexedPrimitives(vertexFeeder.PrimitiveType, mVB.Offset, 0, mVB.VertexCount, mIB.Offset, vertexFeeder.PrimitiveSize);
                            }
                            else
                            {
                                this.m_d3dDevice.DrawPrimitives(vertexFeeder.PrimitiveType, mVB.Offset, vertexFeeder.PrimitiveSize);
                            }
                        }
                    }
                    else if (null != renderable.GetVertexBuffer<Vertex>(out sCount, out primType))
                    {
                        ManagedVertexBuffer<Vertex> mVB = renderable.GetVertexBuffer<Vertex>(out sCount, out primType);
                        this.m_d3dDevice.DrawPrimitives(primType, mVB.Offset, sCount);
                    }
                    else if (null != renderable.GetRawVertexBuffer<Vertex>(out sCount, out primType))
                    {
                        Vertex[] vertices = renderable.GetRawVertexBuffer<Vertex>(out sCount, out primType);
                        this.m_d3dDevice.DrawUserPrimitives<Vertex>(primType, sCount, vertices);
                    }
                }
                this.m_effect.EndPass();
            }
        }

        override public void RenderAllTransparents()
        {
            Matrix matWorld;
            for (int i = 0; this.m_sPasses > i; ++i)
            {
                this.m_effect.BeginPass(i);
                foreach (IRenderable renderable in this.m_lstRenderables)
                {
                    // check and skip any renderable outside current renderer context scop
                    if ((null != renderable.RestrictedRendererContexts) && (false == renderable.RestrictedRendererContexts.Contains(this.RendererContext)))
                    {
                        continue;
                    }
                    // check and slip any fully opaque renderable
                    IDiffuse diffuse = renderable as IDiffuse;
                    if ((null == diffuse) || (0.0f == diffuse.Diffuse.Alpha) || (1.0f == diffuse.Diffuse.Alpha))
                    {
                        continue;
                    }
                    PrimitiveType primType;
                    int sCount;
                    matWorld = renderable.World;
                    this.m_effect.SetValue<Matrix>(this.m_hWorldViewProj, this.RendererContext.Camera.ComputeWorldViewProj(ref matWorld, true));
                    this.m_effect.SetTexture(this.m_hAlbedoTexture, (renderable as IAlbedoTexture).Albedo.Texture);
                    this.m_effect.SetValue<SlimDX.Vector4>(this.m_hDiffuseColor, (renderable as IDiffuse).Diffuse.ToVector4());
                    this.m_effect.CommitChanges();
                    if ((null != renderable.VertexFeeders) && (0 < renderable.VertexFeeders.Count))
                    {
                        foreach (VertexFeeder vertexFeeder in renderable.VertexFeeders)
                        {
                            ManagedVertexBuffer<Vertex> mVB = vertexFeeder.VertexBuffer;
                            if (0 != vertexFeeder.IndexCount)
                            {
                                ManagedIndexBuffer<int> mIB = vertexFeeder.IndexBuffer;
                                this.m_d3dDevice.DrawIndexedPrimitives(vertexFeeder.PrimitiveType, mVB.Offset, 0, mVB.VertexCount, mIB.Offset, vertexFeeder.PrimitiveSize);
                            }
                            else
                            {
                                this.m_d3dDevice.DrawPrimitives(vertexFeeder.PrimitiveType, mVB.Offset, vertexFeeder.PrimitiveSize);
                            }
                        }
                    }
                    else if (null != renderable.GetVertexBuffer<Vertex>(out sCount, out primType))
                    {
                        ManagedVertexBuffer<Vertex> mVB = renderable.GetVertexBuffer<Vertex>(out sCount, out primType);
                        this.m_d3dDevice.DrawPrimitives(primType, mVB.Offset, sCount);
                    }
                    else if (null != renderable.GetRawVertexBuffer<Vertex>(out sCount, out primType))
                    {
                        Vertex[] vertices = renderable.GetRawVertexBuffer<Vertex>(out sCount, out primType);
                        this.m_d3dDevice.DrawUserPrimitives<Vertex>(primType, sCount, vertices);
                    }
                }
                this.m_effect.EndPass();
            }
        }

        override public void Render(IRenderable _renderable)
        {
            // check and skip any renderable outside current renderer context scop
            if ((null == _renderable.RestrictedRendererContexts)
                || (false != _renderable.RestrictedRendererContexts.Contains(this.RendererContext)))
            {
                Matrix matWorld;
                for (int i = 0; this.m_sPasses > i; ++i)
                {
                    this.m_effect.BeginPass(i);
                    PrimitiveType primType;
                    int sCount;
                    matWorld = _renderable.World;
                    this.m_effect.SetValue<Matrix>(this.m_hWorldViewProj, this.RendererContext.Camera.ComputeWorldViewProj(ref matWorld, true));
                    this.m_effect.SetTexture(this.m_hAlbedoTexture, (_renderable as IAlbedoTexture).Albedo.Texture);
                    this.m_effect.SetValue<SlimDX.Vector4>(this.m_hDiffuseColor, (_renderable as IDiffuse).Diffuse.ToVector4());
                    this.m_effect.CommitChanges();
                    if ((null != _renderable.VertexFeeders) && (0 < _renderable.VertexFeeders.Count))
                    {
                        foreach (VertexFeeder vertexFeeder in _renderable.VertexFeeders)
                        {
                            ManagedVertexBuffer<Vertex> mVB = vertexFeeder.VertexBuffer;
                            if (0 != vertexFeeder.IndexCount)
                            {
                                ManagedIndexBuffer<int> mIB = vertexFeeder.IndexBuffer;
                                this.m_d3dDevice.DrawIndexedPrimitives(vertexFeeder.PrimitiveType, mVB.Offset, 0, mVB.VertexCount, mIB.Offset, vertexFeeder.PrimitiveSize);
                            }
                            else
                            {
                                this.m_d3dDevice.DrawPrimitives(vertexFeeder.PrimitiveType, mVB.Offset, vertexFeeder.PrimitiveSize);
                            }
                        }
                    }
                    else if (null != _renderable.GetVertexBuffer<Vertex>(out sCount, out primType))
                    {
                        ManagedVertexBuffer<Vertex> mVB = _renderable.GetVertexBuffer<Vertex>(out sCount, out primType);
                        this.m_d3dDevice.DrawPrimitives(primType, mVB.Offset, sCount);
                    }
                    else if (null != _renderable.GetRawVertexBuffer<Vertex>(out sCount, out primType))
                    {
                        Vertex[] vertices = _renderable.GetRawVertexBuffer<Vertex>(out sCount, out primType);
                        this.m_d3dDevice.DrawUserPrimitives<Vertex>(primType, sCount, vertices);
                    }
                    this.m_effect.EndPass();
                }
            }
        }

        public override void EndRender()
        {
            this.m_d3dDevice.Indices = null;
            base.EndRender();
        }

        override public ManagedVertexBuffer<T> CreateBuffer<T>(int _sSize)
        {
            if (typeof(T) == typeof(Vertex))
            {
                return this.m_vbManager.Create(_sSize) as ManagedVertexBuffer<T>;
            }
            return null;
        }

        override public void ReleaseBuffer<T>(ManagedVertexBuffer<T> _mVB)
        {
            if (typeof(T) == typeof(Vertex))
            {
                this.m_vbManager.Release(_mVB as ManagedVertexBuffer<Vertex>);
            }
        }

        override public IVertexFeeder CreateFeeder(int _sSize)
        {
            return new VertexFeeder(this.CreateBuffer<Vertex>(_sSize), null);
        }

        override public List<IVertexFeeder> CreateFeeders(IShape _shape)
        {
            List<IVertexFeeder> lstFeeder = new List<IVertexFeeder>(_shape.TopologyCount);
            foreach (IShapeTopology topology in _shape.Topologies)
            {
                IVertexFeeder feeder = new VertexFeeder(this.CreateBuffer<Vertex>(_shape.VertexCount), this.m_ibManager.Create(topology.Count));
                // fill vertex buffer
                if (false != feeder.VertexLock())
                {
                    Hashtable htblData = new Hashtable(3); // 3 fields : position, color and uv
                    htblData.Add("POSITION", null);
                    htblData.Add("COLOR", null);
                    htblData.Add("UV", null);
                    uint uIndex = 0;
                    foreach (IShapeVertex vertex in _shape.Vertices)
                    {
                        htblData["POSITION"] = vertex.GetElement<Vector3>("POSITION");
                        htblData["COLOR"] = vertex.GetElement<Color4>("COLOR");
                        htblData["UV"] = vertex.GetElement<Vector2>("UV");
                        feeder.SetVertex(uIndex, htblData);
                        ++uIndex;
                    }
                    feeder.VertexUnlock();
                }
                // fill index buffer
                if (false != feeder.IndexLock())
                {
                    uint uIndex = 0;
                    foreach (int vertexIndex in topology)
                    {
                        feeder.SetIndex(uIndex, vertexIndex);
                        ++uIndex;
                    }
                    feeder.IndexUnlock();
                }
                // 
                feeder.PrimitiveType = topology.PrimitiveType;
                feeder.PrimitiveSize = topology.PrimitiveSize;
                //
                lstFeeder.Add(feeder);
            }
            return lstFeeder;
        }

        override public void ReleaseFeeder(IVertexFeeder _vertexFeeder)
        {
            if (_vertexFeeder.GetType() == typeof(VertexFeeder))
            {
                VertexFeeder vertexFeeder = _vertexFeeder as VertexFeeder;
                this.ReleaseBuffer<Vertex>(vertexFeeder.VertexBuffer);
                if (null != vertexFeeder.IndexBuffer)
                {
                    this.m_ibManager.Release(vertexFeeder.IndexBuffer);
                }
            }
        }

        #endregion

        #region IManagedResource Membres

        public void DeviceLost()
        {
            this.m_vertexDecl.Dispose();
            this.m_vertexDecl = null;
            this.m_hTechnique = null;
            this.m_sPasses = 0;
            this.m_hWorldViewProj = null;
            this.m_effect.Dispose();
            this.m_effect = null;
        }

        public void DeviceReset()
        {
            VertexElement[] ve = new VertexElement[] {
                new VertexElement(0, 0, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Position, 0),
        		new VertexElement(0, 12, DeclarationType.Float4, DeclarationMethod.Default, DeclarationUsage.Color, 0),
        		new VertexElement(0, 28, DeclarationType.Float2, DeclarationMethod.Default, DeclarationUsage.TextureCoordinate, 0),
				VertexElement.VertexDeclarationEnd
            };

            this.m_vertexDecl = new VertexDeclaration(m_d3dDevice, ve);
            string strErrors;
            string strErrors2;
            try
            {
                this.m_effect = SlimDX.Direct3D9.Effect.FromFile(
                    this.m_d3dDevice,
                    "Data/Effects/TextureDiffuse.fx",
                    null,
                    null,
                    null,
                    ShaderFlags.Debug,
                    null,
                    out strErrors);
                this.m_hTechnique = this.m_effect.GetTechnique(0);
                this.m_sPasses = this.m_effect.GetTechniqueDescription(this.m_hTechnique).Passes;
                this.m_hWorldViewProj = this.m_effect.GetParameterBySemantic(null, "WorldViewProjection");
                this.m_hAlbedoTexture = this.m_effect.GetParameterBySemantic(null, "AlbedoTexture");
                this.m_hDiffuseColor = this.m_effect.GetParameterBySemantic(null, "DiffuseColor");
            }
            catch (Exception e)
            {
                strErrors2 = e.Message;
            }
        }

        #endregion
    }
}
